package com.dimples.dd.system.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeNode;
import cn.hutool.core.lang.tree.TreeNodeConfig;
import cn.hutool.core.lang.tree.TreeUtil;
import cn.hutool.core.lang.tree.parser.DefaultNodeParser;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.nacos.shaded.com.google.common.annotations.VisibleForTesting;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dimples.dd.common.constant.DDConstant;
import com.dimples.dd.common.enums.CommonStatusEnum;
import com.dimples.dd.common.pojo.PageResult;
import com.dimples.dd.common.result.ResultCode;
import com.dimples.dd.common.util.collection.CollectionUtils;
import com.dimples.dd.mybatis.core.query.LambdaQueryWrapperX;
import com.dimples.dd.security.core.util.SecurityUtils;
import com.dimples.dd.system.convert.MenuConverter;
import com.dimples.dd.system.core.dal.redis.RedisKeyConstants;
import com.dimples.dd.system.core.enums.MenuTypeEnum;
import com.dimples.dd.system.enums.permission.HrefOpenTypeEnum;
import com.dimples.dd.system.mapper.SystemMenuMapper;
import com.dimples.dd.system.mapper.SystemRoleMenuMapper;
import com.dimples.dd.system.model.bo.RouteBO;
import com.dimples.dd.system.model.bo.SimpleMenuBO;
import com.dimples.dd.system.model.entity.SystemMenuDO;
import com.dimples.dd.system.model.entity.SystemRoleDO;
import com.dimples.dd.system.model.entity.SystemUsersDO;
import com.dimples.dd.system.model.form.MenuForm;
import com.dimples.dd.system.model.query.MenuPageQuery;
import com.dimples.dd.system.model.vo.MenuTreeVO;
import com.dimples.dd.system.model.vo.RouteVO;
import com.dimples.dd.system.service.PermissionService;
import com.dimples.dd.system.service.SystemMenuService;
import com.dimples.dd.system.service.SystemRoleService;
import com.dimples.dd.system.service.SystemUsersService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.dimples.dd.common.exception.BizExceptionUtil.exception;
import static com.dimples.dd.common.exception.ErrorCode.*;

@Service
@RequiredArgsConstructor
public class SystemMenuServiceImpl extends ServiceImpl<SystemMenuMapper, SystemMenuDO> implements SystemMenuService {

    private final MenuConverter menuConverter;

    @Autowired
    private SystemUsersService systemUsersService;

    @Autowired
    private SystemRoleMenuMapper systemRoleMenuMapper;

    @Autowired
    private SystemRoleService systemRoleService;

    @Autowired
    private PermissionService permissionService;

    @Override
    public List<SystemMenuDO> findUserPermissions(String username) {
        return this.baseMapper.findUserPermissions(username);
    }

    @Override
    public RouteVO listRoutes() {
        RouteVO routeVO = new RouteVO();
        Long userId = SecurityUtils.getUserId();
        if (ObjectUtil.isEmpty(userId)) {
            throw exception(ResultCode.TOKEN_PARSE_USER_ID_ERROR);
        }
        // 1.1 获得用户信息
        SystemUsersDO user = this.systemUsersService.getById(userId);
        if (ObjectUtil.isEmpty(user)) {
            routeVO.setRoutes(CollUtil.newArrayList());
            routeVO.setHome("");
            return routeVO;
        }
        // 1.2 查询用户首页
        routeVO.setHome(this.systemRoleMenuMapper.selectHomePageByUserId(userId));

        // 1.3 获得角色列表
        // todo: 暂时获取用户的所有角色下的所有菜单，是否考虑用户存在多个角色时，根据前端切换角色来获取不同角色的不同菜单
        Set<Long> roleIds = this.permissionService.getUserRoleIdListByUserId(userId);
        if (CollUtil.isEmpty(roleIds)) {
            routeVO.setRoutes(CollUtil.newArrayList());
            return routeVO;
        }
        List<SystemRoleDO> roles = this.systemRoleService.getRoleList(roleIds);
        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getValue().equals(role.getStatus().intValue())); // 移除禁用的角色

        // 1.4 获得菜单列表
        Set<Long> menuIds = this.permissionService.getRoleMenuListByRoleId(CollectionUtils.convertSet(roles, SystemRoleDO::getId));
        // 当 ids 为空时，返回一个空的实例对象
        if (CollUtil.isEmpty(menuIds)) {
            routeVO.setRoutes(CollUtil.newArrayList());
            return routeVO;
        } else {
            List<SystemMenuDO> menuList = this.baseMapper.selectBatchIds(menuIds);

            menuList.removeIf(menu ->
                    CommonStatusEnum.DISABLE.getValue().equals(menu.getStatus()) // 移除禁用的菜单
                            || MenuTypeEnum.BUTTON.getValue().equals(menu.getMenuType()) // 移除按钮菜单
            );

            List<RouteBO> routeBOList = this.menuConverter.entityList2BoList(menuList);
            List<RouteVO.Routes> routeList = buildRoutes(DDConstant.ROOT_NODE_ID, routeBOList);

            routeVO.setRoutes(routeList.stream().sorted(Comparator.comparingInt(o -> o.getMeta().getSort())).toList());
        }
        return routeVO;
    }


    /**
     * 递归生成菜单路由层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 路由层级列表
     */
    private List<RouteVO.Routes> buildRoutes(Long parentId, List<RouteBO> menuList) {
        List<RouteVO.Routes> routeList = new ArrayList<>();

        for (RouteBO menu : menuList) {
            if (menu.getParentId().equals(parentId)) {
                RouteVO.Routes routes = toRouteVo(menu);
                List<RouteVO.Routes> children = buildRoutes(menu.getId(), menuList);
                if (!children.isEmpty()) {
                    routes.setChildren(children);
                }
                routeList.add(routes);
            }
        }

        return routeList;
    }

    /**
     * 根据RouteBO创建RouteVO
     */
    private RouteVO.Routes toRouteVo(RouteBO routeBO) {
        RouteVO.Routes routes = new RouteVO.Routes();
        routes.setName(routeBO.getRouteName());
        routes.setPath(routeBO.getPath());
        routes.setRedirect(routeBO.getRedirect());
        routes.setComponent(routeBO.getComponent());

        RouteVO.Routes.Meta meta = RouteVO.createMeta();
        meta.setTitle(routeBO.getName());
        meta.setIcon(routeBO.getIcon());
        meta.setRoles(routeBO.getRoles());
        meta.setSort(routeBO.getSort());
        meta.setHideInMenu(routeBO.getHideInMenu());
        // 【菜单】是否开启页面缓存
        meta.setKeepAlive(routeBO.getKeepAlive());
        // 【目录】只有一个子路由是否始终显示
        if (MenuTypeEnum.CATALOG.getValue().equals(routeBO.getType())) {
            meta.setAlwaysShow(routeBO.getAlwaysShow());
        }
        meta.setActiveMenu(routeBO.getActiveMenu());
        meta.setMultiTab(routeBO.getMultiTab());
        if (routeBO.getFixedIndexInTab() > 0) {
            meta.setFixedIndexInTab(routeBO.getFixedIndexInTab());
        }
        meta.setI18nKey(routeBO.getI18nKey());

        RouteVO.Routes.Props props = RouteVO.createProps();
        if (ObjectUtil.equals(routeBO.getHrefOpenType(), HrefOpenTypeEnum.CURRENT.getType())) {
            props.setUrl(routeBO.getHref());
        } else {
            meta.setHref(routeBO.getHref());
        }

        routes.setMeta(meta);
        routes.setProps(props);
        return routes;
    }

    @Override
    public Boolean existRoute(String routeName) {
        return this.baseMapper.existRoute(routeName);
    }

    @Override
    public List<MenuTreeVO> menuTree(MenuPageQuery menuPageQuery) {
        List<SystemMenuDO> menuList = this.baseMapper.selectList(menuPageQuery);

        Set<Long> parentIds = menuList.stream().map(SystemMenuDO::getParentId).collect(Collectors.toSet());
        Set<Long> menuIds = menuList.stream().map(SystemMenuDO::getId).collect(Collectors.toSet());
        // 获取根节点ID
        List<Long> rootIds = CollUtil.subtractToList(parentIds, menuIds);

        // 使用递归函数来构建菜单树
        return rootIds.stream()
                .flatMap(rootId -> buildMenuTree(rootId, menuList).stream())
                .collect(Collectors.toList());
    }

    /**
     * 递归生成菜单列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 菜单列表
     */
    private List<MenuTreeVO> buildMenuTree(Long parentId, List<SystemMenuDO> menuList) {
        return CollectionUtil.emptyIfNull(menuList)
                .stream()
                .filter(menu -> menu.getParentId().equals(parentId))
                .map(entity -> {
                    MenuTreeVO menuVO = menuConverter.entity2Vo(entity);
                    List<MenuTreeVO> children = buildMenuTree(entity.getId(), menuList);
                    menuVO.setChildren(children);
                    return menuVO;
                }).toList();
    }

    @Override
    public List<Tree<Long>> simpleMenuTree() {
        List<SimpleMenuBO> menuList = this.baseMapper.selectSimpleMenu();
        List<TreeNode<Long>> nodeList = CollUtil.newArrayList();
        menuList.forEach(item -> nodeList.add(new TreeNode<>(item.getId(), item.getParentId(), item.getMenuName(), item.getSort())));
        TreeNodeConfig config = new TreeNodeConfig().setWeightKey("sort").setNameKey("label").setParentIdKey("pId").setIdKey("id");

        return TreeUtil.build(nodeList, DDConstant.ROOT_NODE_ID, config, new DefaultNodeParser<>());
    }

    @Override
    public List<String> menuPagesAll() {
        return this.baseMapper.selectList().stream().map(SystemMenuDO::getRouteName).filter(StrUtil::isNotBlank).collect(Collectors.toList());
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#menuForm.permission",
            condition = "#menuForm.permission != null")
    public Long createMenu(MenuForm menuForm) {
        // 校验父菜单存在
        validateParentMenu(menuForm.getParentId(), null);
        // 校验菜单（自己）
        validateMenu(menuForm.getParentId(), menuForm.getMenuName(), null);

        // 插入数据库
        SystemMenuDO menu = menuConverter.form2Entity(menuForm);
        initMenuProperty(menu);
        String treePath = generateMenuTreePath(menuForm.getParentId());
        menu.setTreePath(treePath);

        int insert = this.baseMapper.insert(menu);
        if (insert > 0) {
            // 编辑刷新角色权限缓存
            if (menuForm.getId() != null) {
                this.permissionService.refreshRolePermsCache();
            }
        }
        // 返回
        return menu.getId();
    }

    /**
     * 初始化菜单的通用属性。
     * <p>
     * 例如说，只有目录或者菜单类型的菜单，才设置 icon
     *
     * @param menu 菜单
     */
    private void initMenuProperty(SystemMenuDO menu) {
        String path = menu.getRoutePath();
        // 菜单为按钮类型时，无需 component、icon、path 属性，进行置空
        if (MenuTypeEnum.BUTTON.getValue().equals(menu.getMenuType())) {
            menu.setComponent("");
            menu.setRouteName("");
            menu.setIcon("");
            menu.setRoutePath("");
        }
        // 如果是目录
        if (MenuTypeEnum.CATALOG.getValue().equals(menu.getMenuType())) {
            if (menu.getParentId() == 0 && !path.startsWith(StrUtil.SLASH)) {
                menu.setRoutePath(StrUtil.SLASH + path); // 一级目录需以 / 开头
            }
        }
        // 如果是外链
        else if (MenuTypeEnum.EXTLINK.getValue().equals(menu.getMenuType())) {
            menu.setComponent(DDConstant.EXT_LINK_DEFAULT_COMPONENT);
        }
    }

    /**
     * 菜单路径生成
     *
     * @param parentId 父ID
     * @return 父节点路径以英文逗号(, )分割，eg: 1,2,3
     */
    private String generateMenuTreePath(Long parentId) {
        if (DDConstant.ROOT_NODE_ID.equals(parentId)) {
            return String.valueOf(parentId);
        } else {
            SystemMenuDO parent = this.getById(parentId);
            ArrayList<Object> tmp = CollUtil.newArrayList();
            if (ObjectUtil.isEmpty(parent)) {
                return null;
            }
            if (StrUtil.isNotBlank(parent.getTreePath())) {
                tmp.add(parent.getTreePath());
            }
            tmp.add(parent.getId());
            return StrUtil.join(StrUtil.COMMA, tmp);
        }
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为 permission 如果变更，涉及到新老两个 permission。直接清理，简单有效
    public void updateMenu(Long id, MenuForm menuForm) {
        menuForm.setId(id);
        // 校验更新的菜单是否存在
        if (this.baseMapper.selectById(id) == null) {
            throw exception(MENU_NOT_EXISTS);
        }
        // 校验父菜单存在
        validateParentMenu(menuForm.getParentId(), menuForm.getId());
        // 校验菜单（自己）
        validateMenu(menuForm.getParentId(), menuForm.getMenuName(), menuForm.getId());

        // 更新到数据库
        SystemMenuDO updateObj = menuConverter.form2Entity(menuForm);
        initMenuProperty(updateObj);
        this.baseMapper.updateById(updateObj);
    }

    @Override
    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
            allEntries = true) // allEntries 清空所有缓存，因为此时不知道 id 对应的 permission 是多少。直接清理，简单有效
    public void deleteMenu(Long id) {
        // 校验是否还有子菜单
        if (this.baseMapper.selectCountByParentId(id) > 0) {
            throw exception(MENU_EXISTS_CHILDREN);
        }
        // 校验删除的菜单是否存在
        if (this.baseMapper.selectById(id) == null) {
            throw exception(MENU_NOT_EXISTS);
        }
        // 标记删除
        this.baseMapper.deleteById(id);
        // 删除授予给角色的权限
        this.permissionService.processMenuDeleted(id);
    }

    @Override
    public PageResult<SystemMenuDO> selectPage(MenuPageQuery menuPageQuery) {
        LambdaQueryWrapperX<SystemMenuDO> wrapperX = new LambdaQueryWrapperX<>();
        wrapperX.eqIfPresent(SystemMenuDO::getStatus, menuPageQuery.getStatus()).eqIfPresent(SystemMenuDO::getMenuName, menuPageQuery.getMenuName());
        return this.baseMapper.selectPage(menuPageQuery, wrapperX);
    }

    /**
     * 校验父菜单是否合法
     * <p>
     * 1. 不能设置自己为父菜单
     * <p>
     * 2. 父菜单不存在
     * <p>
     * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
     *
     * @param parentId 父菜单编号
     * @param childId  当前菜单编号
     */
    @VisibleForTesting
    void validateParentMenu(Long parentId, Long childId) {
        if (parentId == null || DDConstant.ROOT_NODE_ID.equals(parentId)) {
            return;
        }
        // 不能设置自己为父菜单
        if (ObjectUtil.equals(parentId, childId)) {
            throw exception(MENU_PARENT_ERROR);
        }
        SystemMenuDO menu = this.baseMapper.selectById(parentId);
        // 父菜单不存在
        if (menu == null) {
            throw exception(MENU_PARENT_NOT_EXISTS);
        }
        // 父菜单必须是目录或者菜单类型
        if (!MenuTypeEnum.CATALOG.getValue().equals(menu.getMenuType())
                && !MenuTypeEnum.MENU.getValue().equals(menu.getMenuType())) {
            throw exception(MENU_PARENT_NOT_DIR_OR_MENU);
        }
    }

    /**
     * 校验菜单是否合法
     * <p>
     * 1. 校验相同父菜单编号下，是否存在相同的菜单名
     *
     * @param name     菜单名字
     * @param parentId 父菜单编号
     * @param id       菜单编号
     */
    @VisibleForTesting
    void validateMenu(Long parentId, String name, Long id) {
        SystemMenuDO menu = this.baseMapper.selectByParentIdAndName(parentId, name);
        if (menu == null) {
            return;
        }
        // 如果 id 为空，说明不用比较是否为相同 id 的菜单
        if (id == null) {
            throw exception(MENU_NAME_DUPLICATE);
        }
        if (!menu.getId().equals(id)) {
            throw exception(MENU_NAME_DUPLICATE);
        }
    }
}


















